package drds.plus.repository.mysql.spi;

import com.google.common.base.Joiner;
import drds.plus.executor.cursor.cursor.Cursor;
import drds.plus.executor.cursor.cursor.ISortingCursor;
import drds.plus.executor.cursor.cursor.impl.DuplicateValueRowDataLinkedList;
import drds.plus.executor.cursor.cursor_metadata.CursorMetaData;
import drds.plus.executor.cursor.cursor_metadata.CursorMetaDataImpl;
import drds.plus.executor.record_codec.record.FixedLengthRecord;
import drds.plus.executor.record_codec.record.KeyValueRecordPair;
import drds.plus.executor.record_codec.record.NamedRecord;
import drds.plus.executor.record_codec.record.Record;
import drds.plus.executor.row_values.RowValues;
import drds.plus.executor.utils.Utils;
import drds.plus.sql_process.abstract_syntax_tree.ObjectCreateFactory;
import drds.plus.sql_process.abstract_syntax_tree.configuration.ColumnMetaData;
import drds.plus.sql_process.abstract_syntax_tree.configuration.parse.TableMetaDataParser;
import drds.plus.sql_process.abstract_syntax_tree.execute_plan.ExecutePlan;
import drds.plus.sql_process.abstract_syntax_tree.execute_plan.query.QueryWithIndex;
import drds.plus.sql_process.abstract_syntax_tree.expression.item.column.Column;
import drds.plus.sql_process.abstract_syntax_tree.expression.item.function.BooleanFilter;
import drds.plus.sql_process.abstract_syntax_tree.expression.item.function.Function;
import drds.plus.sql_process.abstract_syntax_tree.expression.item.function.FunctionName;
import drds.plus.sql_process.abstract_syntax_tree.expression.item.function.Operation;
import drds.plus.sql_process.type.Type;
import drds.plus.sql_process.utils.DnfFilters;
import drds.plus.util.GeneralUtil;

import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.*;
import java.util.Map.Entry;

public class CursorImpl implements Cursor {

    public int sizeLimination = 10000;
    protected JdbcHandler jdbcHandler;
    protected ExecutePlan executePlan;
    protected CursorMetaData cursorMetaData;
    protected boolean inited = false;
    protected boolean isStreaming = false;
    protected List<ColumnMetaData> columnMetaDataList = null;

    public CursorImpl(ExecutePlan executePlan, boolean isStreaming, CursorMetaData cursorMetaData, JdbcHandler jdbcHandler) {
        super();
        this.jdbcHandler = jdbcHandler;
        this.executePlan = executePlan;
        this.cursorMetaData = cursorMetaData;
        this.isStreaming = isStreaming;
    }

    public boolean skipTo(Record keyRecord) throws RuntimeException {
        init();
        return true;
    }

    public boolean skipTo(KeyValueRecordPair keyKeyValueRecordPair) throws RuntimeException {
        throw new UnsupportedOperationException("not support yet");
    }

    public RowValues current() throws RuntimeException {
        init();
        return jdbcHandler.getCurrentRowData();
    }

    public RowValues next() throws RuntimeException {
        init();
        try {
            return jdbcHandler.next();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public void init() throws RuntimeException {
        if (inited) {
            return;
        }

        try {
            jdbcHandler.executeQuery(cursorMetaData, isStreaming);
            buildCursorMetaData();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        inited = true;

    }

    void buildCursorMetaData() throws RuntimeException {
        if (columnMetaDataList != null) {
            return;
        }

        columnMetaDataList = new ArrayList();

        // 使用meta做为returncolumns
        // resultset中返回的meta信是物理表名，会导致join在构造返回对象时找不到index(表名不同/为null)
        if (cursorMetaData != null) {
            columnMetaDataList.addAll(cursorMetaData.getColumnMetaDataList());
        } else {
            try {
                if (this.jdbcHandler.getResultSet() == null) {
                    jdbcHandler.executeQuery(cursorMetaData, isStreaming);
                }

                ResultSetMetaData resultSetMetaData = this.jdbcHandler.getResultSet().getMetaData();
                for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
                    boolean isUnsigned = resultSetMetaData.getColumnTypeName(i).contains("unsigned");//containsIgnoreCase这个函数
                    Type type = TableMetaDataParser.jdbcTypeToDataType(resultSetMetaData.getColumnType(i), isUnsigned);
                    String columnLabel = resultSetMetaData.getColumnLabel(i);
                    ColumnMetaData columnMetaData = new ColumnMetaData(null, columnLabel, null, type, true);
                    columnMetaDataList.add(columnMetaData);
                }

                cursorMetaData = CursorMetaDataImpl.buildCursorMetaData(columnMetaDataList);
                jdbcHandler.setCursorMetaData(cursorMetaData, isStreaming);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

    }

    public ISortingCursor getResultSet() throws RuntimeException {
        init();

        return jdbcHandler.getResultCursor();
    }

    public RowValues prev() throws RuntimeException {
        isStreaming = false;
        init();
        try {
            return jdbcHandler.prev();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public RowValues first() throws RuntimeException {
        init();
        try {
            return jdbcHandler.first();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public RowValues last() throws RuntimeException {
        init();
        try {
            return jdbcHandler.last();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public boolean delete() throws RuntimeException {
        throw new UnsupportedOperationException("not support yet");
    }

    public RowValues getNextDuplicateValueRowData() throws RuntimeException {
        throw new UnsupportedOperationException("not support yet");
    }

    public void put(Record key, Record value) throws RuntimeException {
        throw new UnsupportedOperationException("not support yet");
    }

    public CursorMetaData getCursorMeta() {
        return cursorMetaData;
    }

    public void setCursorMeta(CursorMetaData cursorMeta) {
        this.cursorMetaData = cursorMeta;
    }


    public List<RuntimeException> close(List<RuntimeException> runtimeExceptionList) {
        if (runtimeExceptionList == null) {
            runtimeExceptionList = new ArrayList();
        }
        try {
            jdbcHandler.close();

        } catch (Exception e) {
            runtimeExceptionList.add(new RuntimeException(e));
        }

        return runtimeExceptionList;
    }

    /**
     * 核心方法   批量查询数据
     *
     * @param keyRecordList
     * @param prefixMatch
     * @param keyFilterOrValueFilter 为true使用keyFilter，为false使用valueFilter
     * @return
     * @throws RuntimeException
     */
    public Map<Record, DuplicateValueRowDataLinkedList> mgetRecordToDuplicateValueRowDataLinkedListMap(List<Record> keyRecordList, boolean prefixMatch, boolean keyFilterOrValueFilter) throws RuntimeException {

        QueryWithIndex queryWithIndex = (QueryWithIndex) executePlan.copy();
        BooleanFilter booleanFilter = ObjectCreateFactory.createBooleanFilter();
        booleanFilter.setOperation(Operation.in);
        booleanFilter.setValueList(new ArrayList<Object>());

        for (Record record : keyRecordList) {
            Map<String, Object> recordMap = record.getColumnNameToValueMap();
            if (recordMap.size() == 1) {
                // 单字段in
                Entry<String, Object> entry = recordMap.entrySet().iterator().next();

                String columnName = entry.getKey();
                Object value = entry.getValue();
                //
                Column column = ObjectCreateFactory.createColumn();
                column.setColumnName(columnName);
                column.setType(record.getValueType(0));
                column.setTableName(queryWithIndex.getAlias());
                booleanFilter.setColumn(column);
                booleanFilter.getValueList().add(value);
            } else {
                // 多字段in
                if (booleanFilter.getColumn() == null) {
                    booleanFilter.setColumn(buildRowFunction(recordMap.keySet(), true, record));
                }
                booleanFilter.getValueList().add(buildRowFunction(recordMap.values(), false, record));

            }
        }

        queryWithIndex.setKeyFilter(DnfFilters.and(queryWithIndex.getKeyFilter(), booleanFilter));
        jdbcHandler.setExecutePlan(queryWithIndex);
        try {
            jdbcHandler.executeQuery(this.cursorMetaData, isStreaming);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

        buildCursorMetaData();
        Map<Record, DuplicateValueRowDataLinkedList> res = buildRecordToDuplicateValueLinkedListMap(keyRecordList);
        return res;
    }


    public Map<Record, DuplicateValueRowDataLinkedList> buildRecordToDuplicateValueLinkedListMap(List<Record> recordList) throws RuntimeException {
        String columnName = recordList.get(0).getColumnNameList().get(0);
        ColumnMetaData columnMetaData = new ColumnMetaData(getCursorMeta().getColumnMetaDataList().get(0).getTableName(), columnName, null, null, true);
        List<ColumnMetaData> columnMetaDataList = new LinkedList<ColumnMetaData>();
        columnMetaDataList.add(columnMetaData);
        RowValues rowData = null;
        int count = 0;
        Map<Record, DuplicateValueRowDataLinkedList> recordToDuplicateValueLinkedListMap = new HashMap<Record, DuplicateValueRowDataLinkedList>();
        try {
            while ((rowData = jdbcHandler.next()) != null) {
                Record valueRecord = new FixedLengthRecord(columnMetaDataList);
                Record keyRecord = new NamedRecord(columnName, valueRecord);
                rowData = Utils.fromRowDataToArrayRowData(rowData);
                Object object = Utils.getValue(rowData, columnMetaData);
                valueRecord.put(columnName, object);
                DuplicateValueRowDataLinkedList duplicateValueRowDataLinkedList = recordToDuplicateValueLinkedListMap.get(keyRecord);
                if (duplicateValueRowDataLinkedList == null) {// 加新列
                    duplicateValueRowDataLinkedList = new DuplicateValueRowDataLinkedList(rowData);
                    recordToDuplicateValueLinkedListMap.put(keyRecord, duplicateValueRowDataLinkedList);
                } else {// 加重复列
                    while (duplicateValueRowDataLinkedList.next != null) {
                        duplicateValueRowDataLinkedList = duplicateValueRowDataLinkedList.next;
                    }
                    duplicateValueRowDataLinkedList.next = new DuplicateValueRowDataLinkedList(rowData);
                }
                count++;
                if (count >= sizeLimination) {// 保护。。。别太多了
                    throw new IllegalArgumentException("size is more than limination " + sizeLimination);
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        if (rowData == null) {
            try {
                jdbcHandler.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        return recordToDuplicateValueLinkedListMap;
    }

    public List<DuplicateValueRowDataLinkedList> mgetDuplicateValueRowDataLinkedListList(List<Record> keyRecordList, boolean prefixMatch, boolean keyFilterOrValueFilter) throws RuntimeException {
        Map<Record, DuplicateValueRowDataLinkedList> recordToDuplicateValueMap = mgetRecordToDuplicateValueRowDataLinkedListMap(keyRecordList, prefixMatch, keyFilterOrValueFilter);
        return new ArrayList<DuplicateValueRowDataLinkedList>(recordToDuplicateValueMap.values());
    }

    public String toStringWithInden(int inden) {
        String tabTittle = GeneralUtil.getTab(inden);
        String tabContent = GeneralUtil.getTab(inden + 1);
        StringBuilder sb = new StringBuilder();

        GeneralUtil.printlnToStringBuilder(sb, tabTittle + "MyCursor ");
        if (cursorMetaData != null) {
            GeneralUtil.printAFieldToStringBuilder(sb, "cursorMetaData", this.cursorMetaData, tabContent);
        }

        GeneralUtil.printAFieldToStringBuilder(sb, "isStreaming", this.isStreaming, tabContent);

        if (this.jdbcHandler != null)
            GeneralUtil.printAFieldToStringBuilder(sb, "execute_plan", this.jdbcHandler.getExecutePlan(), tabContent);

        return sb.toString();
    }

    public void beforeFirst() throws RuntimeException {
        init();
        try {
            jdbcHandler.beforeFirst();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public List<ColumnMetaData> getColumnMetaDataList() throws RuntimeException {

        buildCursorMetaData();
        return this.columnMetaDataList;

    }

    public boolean isDone() {
        return true;
    }

    private Function buildRowFunction(Collection values, boolean isColumn, Record record) {
        Function function = ObjectCreateFactory.createFunction();
        function.setFunctionName(FunctionName.row);
        StringBuilder columnName = new StringBuilder();
        columnName.append('(').append(Joiner.on(",").join(values)).append(')');
        function.setColumnName(columnName.toString());
        if (isColumn) {
            List<Column> columnList = new ArrayList<Column>(values.size());
            for (Object value : values) {
                Column column = ObjectCreateFactory.createColumn();
                column.setColumnName((String) value);
                column.setType(record.getValueType((String) value));
                columnList.add(column);
            }

            function.setArgList(columnList);
        } else {
            function.setArgList(new ArrayList(values));
        }
        return function;
    }
}
